package com.psmz.iwi.docanalyzer.core.document.business;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Observer;
import java.util.HashMap;
import java.util.Map;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
//import org.apache.lucene.index.Term;
//import org.apache.lucene.index.TermEnum;
//import org.apache.lucene.index.TermFreqVector;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

import org.apache.commons.math.linear.OpenMapRealVector;
import org.apache.commons.math.linear.RealVectorFormat;
import org.apache.commons.math.linear.SparseRealVector;

import com.psmz.iwi.docanalyzer.core.database.DaoException;
import com.psmz.iwi.docanalyzer.core.database.DataSourceManager;
import com.psmz.iwi.docanalyzer.core.document.entities.Document;
import com.psmz.iwi.docanalyzer.core.document.entities.DocumentSearchResult;
import com.psmz.iwi.docanalyzer.core.document.entities.DocumentType;
import com.psmz.iwi.docanalyzer.core.document.entities.NumberOfWordsClassificationGroup;
import com.psmz.iwi.docanalyzer.core.document.job.DocumentIndexingJob;
import com.psmz.iwi.docanalyzer.core.document.parser.Parser;
import com.psmz.iwi.docanalyzer.core.document.parser.ParserException;
import com.psmz.iwi.docanalyzer.core.document.parser.TextExtractionResult;
import com.psmz.iwi.docanalyzer.core.settings.ApplicationSettings;

/**
 * Usluga obslugujaca repozytorium dokumentow
 * 
 * @author piotrs10
 * 
 */
public class DocumentRepositoryService {
	private ApplicationSettings applicationSettings;

	public DocumentRepositoryService(ApplicationSettings settings) {
		this.applicationSettings = settings;
	}

	/**
	 * Zapisuje do bazy zawartosc dokumentow.
	 * 
	 * @param rootPath
	 *            Sciezka do katalogu, od ktorego ma zaczac sie ladowanie.
	 * @param indexingJobObserver
	 *            Obserwator zadania indeksowania.
	 * @throws DocumentRepositoryServiceException
	 *             Wyrzucany w przypadku bledu przetwarzania.
	 */
	public void storeDocuments(String rootPath, Observer indexingJobObserver)
			throws DocumentRepositoryServiceException {
		Connection connection = null;
		try {
			connection = DataSourceManager.getConnection(applicationSettings);
			DocumentDao documentDao = new DocumentDao(connection);
			File root = new File(rootPath);
			if (!root.exists()) {
				throw new DocumentRepositoryServiceException(
						"Podany katalog nie istnieje");
			} else if (!root.isDirectory()) {
				throw new DocumentRepositoryServiceException(
						"Podany plik nie jest katalogiem");
			}

			// rekurencyjnie przegladamy drzewo katalogow
			LinkedList<File> directoryStack = new LinkedList<File>();
			LinkedList<File> fileStack = new LinkedList<File>();
			directoryStack.push(root);
			File current = null;
			while (!directoryStack.isEmpty()) {
				current = directoryStack.pop();
				File[] files = current.listFiles();
				if (files != null) {
					for (File storedFile : files) {
						if (storedFile.isDirectory()) {
							directoryStack.push(storedFile);
						} else if (storedFile.isFile()) {
							// zapisujemy metadane pliku do bazy
							saveFileInDatabase(storedFile, documentDao);
							fileStack.push(storedFile);
						}
					}
				}
			}
			
			/*IndexReader reader = IndexReader.open(FSDirectory.open(new File(
					applicationSettings.getIndexPath())));
			Map<String,Integer> terms = new HashMap<String,Integer>();
			TermEnum termEnum = reader.terms(new Term("content"));
			int pos = 0;
			while (termEnum.next()) {
				Term term = termEnum.term();
				if (! "content".equals(term.field()))
					break;
				terms.put(term.text(), pos++);
			}
			DocVector[] docs = new DocVector[fileStack.length];
			int i = 0;
			for (int docId : docIds) {
				TermFreqVector[] tfvs = reader.getTermFreqVectors(docId);
				docs[i] = new DocVector(terms);
				for (TermFreqVector tfv : tfvs) {
					String[] termTexts = tfv.getTerms();
					int[] termFreqs = tfv.getTermFrequencies();
					for (int j = 0; j < termTexts.length; j++) {
						docs[i].setEntry(termTexts[j], termFreqs[j]);
					}
				}
				docs[i].normalize();
				i++;
			}
			
			//while (!fileStack.isEmpty()) {
				//current = fileStack.pop();
				//for (File secondFile : fileStack) {
					
					//double cosSim = getCosineSimilarity(current, secondFile);
					//System.out.println(current.getName() + " " + secondFile.getName() + " " + cosSim);
				//}
			//}
			double cosSim = getCosineSimilarity(docs[0], docs[2]);
			System.out.println(cosSim);*/
			
			
			// commit
			DataSourceManager.commitTransaction(connection);
			// uruchamiamy zadanie indeksowania
			startIndexingJob(indexingJobObserver);
		} catch (DaoException e) {
			if (connection != null) {
				try {
					DataSourceManager.rollbackTransaction(connection);
				} catch (DaoException e1) {
					System.err.println("Nieudane wycofanie transakcji");
				}
				System.err.println(e.toString());
				throw new DocumentRepositoryServiceException(
						"Blad dostepu do danych", e);

			}
		} finally {
			if (connection != null) {
				try {
					DataSourceManager.closeConnection(connection);
				} catch (DaoException e) {
					System.err.println("Nieudane zamkniecie polaczenia");
					System.err.println(e.toString());
				}
			}
		}

	}

	/**
	 * Pobiera wszystkie niezaindeksowane dokumenty.
	 * 
	 * @return Wszystkie niezaindeksowane dokumenty.
	 * @throws DocumentRepositoryServiceException
	 */
	public Collection<Document> getNotIndexedDocuments()
			throws DocumentRepositoryServiceException {
		List<Document> notIndexedDocuments = new ArrayList<Document>();
		Connection connection = null;
		try {
			connection = DataSourceManager.getConnection(applicationSettings);
			DocumentDao documentDao = new DocumentDao(connection);
			notIndexedDocuments = documentDao.getAllNotIndexedDocuments();
		} catch (DaoException e) {
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException(
					"Blad dostepu do danych", e);
		} finally {
			if (connection != null) {
				try {
					DataSourceManager.closeConnection(connection);
				} catch (DaoException e) {
					System.err.println("Nieudane zamkniecie polaczenia");
					System.err.println(e.toString());
				}
			}
		}
		return notIndexedDocuments;
	}

	/**
	 * Indeksuje dokumenty
	 * 
	 * @param documents
	 *            Dokumenty do indeksowania.
	 * @throws DocumentRepositoryServiceException
	 */
	public void indexDocuments(Collection<Document> documents)
			throws DocumentRepositoryServiceException {
		IndexWriter indexWriter = null;
		Connection connection = null;
		try {
			connection = DataSourceManager.getConnection(applicationSettings);
			DocumentDao documentDao = new DocumentDao(connection);
			Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_45);
			IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_45,
					analyzer);
			indexWriter = new IndexWriter(FSDirectory.open(new File(
					applicationSettings.getIndexPath())), config);
			for (Document document : documents) {
				Parser parser = document.getType().getParser();
				TextExtractionResult textExtractionResult = parser
						.extractText(document.getFile());
				updateIndex(document, textExtractionResult, indexWriter);
				document.setIndexed(true);
				document.setIndexingDate(new Date());
				document.setNumberOfWords(textExtractionResult
						.getNumberOfWords());
				documentDao.save(document);
			}
			DataSourceManager.commitTransaction(connection);
			indexWriter.commit();
		} catch (ParserException e) {
			try {
				DataSourceManager.rollbackTransaction(connection);
			} catch (DaoException e1) {
				System.err.println(e1.toString());
			}
			rollbackIndexChanges(indexWriter);
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException("Blad analizy pliku",
					e);
		} catch (IOException e) {
			try {
				DataSourceManager.rollbackTransaction(connection);
			} catch (DaoException e1) {
				System.err.println(e1.toString());
			}
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException(
					"Blad wejscia/wyjscia", e);
		} catch (DaoException e) {
			try {
				DataSourceManager.rollbackTransaction(connection);
			} catch (DaoException e1) {
				System.err.println(e1.toString());
			}
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException(
					"Blad wejscia/wyjscia", e);
		} finally {
			if (indexWriter != null) {
				try {
					indexWriter.close();
				} catch (IOException e) {
					System.err.println(e.toString());
				}
			}
			if (connection != null) {
				try {
					DataSourceManager.closeConnection(connection);
				} catch (DaoException e) {
					System.err.println(e.toString());
				}
			}
		}

	}

	public Collection<DocumentSearchResult> documentFullTextSearch(
			String pattern) throws DocumentRepositoryServiceException {
		pattern = "content: " + pattern;
		int hitsPerPage = 100;
		List<DocumentSearchResult> documentSearchResults = new ArrayList<DocumentSearchResult>();
		Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_45);
		IndexReader reader = null;
		try {
			reader = DirectoryReader.open(FSDirectory.open(new File(
					applicationSettings.getIndexPath())));
			IndexSearcher searcher = new IndexSearcher(reader);
			TopScoreDocCollector collector = TopScoreDocCollector.create(
					hitsPerPage, true);
			QueryParser parser = new QueryParser(Version.LUCENE_45, pattern,
					analyzer);
			Query query = parser.parse(pattern);
			searcher.search(query, collector);
			TopDocs topDocs = collector.topDocs();
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
			for (ScoreDoc scoreDoc : scoreDocs) {
				org.apache.lucene.document.Document document = reader
						.document(scoreDoc.doc);
				DocumentSearchResult result = new DocumentSearchResult();
				result.setPath(document.get(Document.DOCUMENT_NAME_FIELD));
				result.setScore(scoreDoc.score);
				documentSearchResults.add(result);
			}
		} catch (IOException e) {
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException(
					"Blad wejscia/wyjscia", e);
		} catch (org.apache.lucene.queryparser.classic.ParseException e) {
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException(
					"Blad parsowania zapytania", e);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					System.err.println("Blad zamykania readera lucene");
					System.err.println(e.toString());
				}
			}
		}
		return documentSearchResults;
	}

	public List<NumberOfWordsClassificationGroup> getClassifiedDocuments()
			throws DocumentRepositoryServiceException {
		List<NumberOfWordsClassificationGroup> groups = new ArrayList<NumberOfWordsClassificationGroup>();
		Connection connection = null;
		try {
			connection = DataSourceManager.getConnection(applicationSettings);
			DocumentDao documentDao = new DocumentDao(connection);
			groups = documentDao.getClassifiedDocuments();
		} catch (DaoException e) {
			System.err.println(e.toString());
			throw new DocumentRepositoryServiceException(
					"Blad dostepu do danych", e);
		} finally {
			if (connection != null) {
				try {
					connection.close();
				} catch (SQLException e) {
					System.err.println(e.toString());
				}
			}
		}
		return groups;
	}

	private void rollbackIndexChanges(IndexWriter indexWriter) {
		if (indexWriter != null) {
			try {
				indexWriter.rollback();
			} catch (IOException e) {
				System.err.println("Nie mozna wycofac transakcji lucene");
				System.err.println(e.toString());
			}
		}

	}

	private void updateIndex(Document document,
			TextExtractionResult textExtractionResult, IndexWriter indexWriter)
			throws IOException {
		org.apache.lucene.document.Document luceneDocument = toLuceneDocument(
				document, textExtractionResult);
		indexWriter.addDocument(luceneDocument);
	}

	private org.apache.lucene.document.Document toLuceneDocument(
			Document document, TextExtractionResult textExtractionResult) {
		final String textContent = textExtractionResult.getText() != null ? textExtractionResult
				.getText() : "";
		org.apache.lucene.document.Document luceneDocument = new org.apache.lucene.document.Document();
		luceneDocument.add(new TextField(Document.DOCUMENT_NAME_FIELD, document
				.getFile().getAbsolutePath(), TextField.Store.YES));
		luceneDocument
				.add(new TextField(Document.CONTENT_FIELD, textContent, Store.YES));
		return luceneDocument;
	}

	private void startIndexingJob(Observer observer) {
		DocumentIndexingJob job = new DocumentIndexingJob(this);
		job.addObserver(observer);
		Thread thread = new Thread(job);
		thread.start();
	}

	private void saveFileInDatabase(File fileToSave, DocumentDao documentDao)
			throws DaoException {
		Document document = new Document();
		DocumentType documentType = extractDocumentType(fileToSave);
		if (documentType != null) {
			document.setFile(fileToSave);
			document.setIndexed(false);
			document.setName(fileToSave.getName());
			document.setType(documentType);
			document.setCreationDate(new Date());
			documentDao.save(document);
		}
	}

	private DocumentType extractDocumentType(File fileToSave) {
		String[] fileNameTokens = fileToSave.getName().split("\\.");
		String extension = fileNameTokens[fileNameTokens.length - 1];
		return DocumentType.getByExtension(extension);
	}
	
	private double getCosineSimilarity(DocVector d1, DocVector d2) {
		return (d1.vector.dotProduct(d2.vector)) /
					(d1.vector.getNorm() * d2.vector.getNorm());
	}
	
	private class DocVector {
	    public Map<String,Integer> terms;
	    public SparseRealVector vector;
	    
	    public DocVector(Map<String,Integer> terms) {
	      this.terms = terms;
	      this.vector = new OpenMapRealVector(terms.size());
	    }
	    
	    public void setEntry(String term, int freq) {
	      if (terms.containsKey(term)) {
	        int pos = terms.get(term);
	        vector.setEntry(pos, (double) freq);
	      }
	    }
	    
	    public void normalize() {
	      double sum = vector.getL1Norm();
	      vector = (SparseRealVector) vector.mapDivide(sum);
	    }
	    
	    public String toString() {
	      RealVectorFormat formatter = new RealVectorFormat();
	      return formatter.format(vector);
	    }
	}
}
